package xpath

import (
	"bytes"
	"strings"

	"gopkg.in/xmlpath.v2"

	"code.google.com/awwwe-browser/data"
)

// Converts an xpath path expression to a []*xmlpath.Node.
// path is a valid xpath path expression.
// context is the xmlpath.Node on which to apply the path.
func PathNodesString(path string, context *xmlpath.Node) []*xmlpath.Node {
	var bfr *bytes.Buffer
	var bb []byte
	var paths []string
	var p string
	var rnodes, nodes []*xmlpath.Node
	var node *xmlpath.Node
	rnodes = make([]*xmlpath.Node, 0, 5)
	paths = strings.Split(path, "|")
	for _, p = range paths {
		bfr = bytes.NewBuffer(data.EmptyBytes)
		bfr.WriteString(p)
		bb = bfr.Bytes()
		nodes = pathNodes(bytes.Runes(bb), context)
		for _, node = range nodes {
			rnodes = append(rnodes, node)
		}
	}
	return rnodes
}

// Converts an xpath path expression to a []*xmlpath.Node.
// path is a valid xpath path expression.
// context is the xmlpath.Node on which to apply the path.
func PathNodesBytes(path []byte, context *xmlpath.Node) []*xmlpath.Node {
	return PathNodesString(string(path), context)
}

// Converts an xpath path expression to a []*xmlpath.Node.
// path is a valid xpath path expression.
// context is the xmlpath.Node on which to apply the path.
func PathNodes(path []rune, context *xmlpath.Node) []*xmlpath.Node {
	return PathNodesString(string(path), context)
}

func pathNodes(path []rune, context *xmlpath.Node) []*xmlpath.Node {
	// ex: "p1 | p2 | p3"
	var cleanpath []rune
	cleanpath = unwrapRunes(path)
	return findDecendents(splitPath(cleanpath), 0, context)
}

func splitPath(path []rune) *data.SliceSliceRune {
	var ssr1, ssr2, temp *data.SliceSliceRune
	var slice, fixed []rune
	var r rune
	var i, j, length1, templength int
	ssr1 = _splitPath(path)
	length1 = ssr1.Len()
	ssr2 = data.NewSliceSliceRune(0, length1)
	for i = 0; i < length1; i++ {
		slice = ssr1.AtIndex(i)
		if slice[0] == parenStartRune && slice[1] == parenStartRune {
			// needs to be split again
			temp = _splitPath(slice[1:])
			templength = temp.Len()
			if templength > 1 {
				ssr2.Append(temp.AtIndex(0))
				for j = 1; j < templength; j++ {
					slice = temp.AtIndex(j)
					fixed = make([]rune, 1, len(slice)+1)
					fixed[0] = parenStartRune
					for _, r = range slice {
						fixed = append(fixed, r)
					}
					ssr2.Append(fixed)
				}
			} else {
				ssr2.Append(slice)
			}
		} else {
			ssr2.Append(slice)
		}
	}
	return ssr2
}

// param path is a path not a boolean expression
// split a path into simpler parts
// /p1/(p2)[2]//p3/p4 --> /p1, /(p2)[2], //p3/p4
// ((/html/body/div[contains(@class, "hfeed")]/div)[2]/div)[1]/div[@role="main"]/div/h2/a
//  currently : ((/html/body/div[contains(@class, "hfeed")]/div)[2]/div)[1]
//            : /div[@role="main"]/div/h2/a
// should be : (/html/body/div[contains(@class, "hfeed")]/div)[2]
//           : (/div)[1]
//           : /div[@role="main"]/div/h2/a
// split at "/("
func _splitPath(path []rune) *data.SliceSliceRune {
	var i, start int
	simplePaths := data.BuildSliceSliceRune(0, 5)
	parenCount := 0
	bracketCount := 0
	startedIndexed := false
	// skip white space
	length := len(path)
	for i = 0; i < length; i++ {
		if strings.ContainsRune(data.WhiteSpaceString, path[i]) == false {
			break
		}
	}
	start = i
	for ; i < length; i++ {
		if path[i] == parenStartRune {
			parenCount++
			if i == start {
				startedIndexed = true
			}
		} else if path[i] == parenEndRune {
			parenCount--
		} else if path[i] == bracketStartRune {
			bracketCount++
		} else if path[i] == bracketEndRune {
			bracketCount--
			if parenCount == 0 && bracketCount == 0 && startedIndexed == true {
				// at ']' of "(/p)[1]"
				// save the path
				simplePaths.Append(path[start : i+1])
				start = i + 1
				startedIndexed = false
			}
		}
	}
	if start < i {
		// save this sub path
		simplePaths.Append(path[start:i])
	}
	return &simplePaths
}

func findDecendents(simplePaths *data.SliceSliceRune, simplePathIndex int, context *xmlpath.Node) []*xmlpath.Node {
	var length int
	var xmlNodes, tempNodes []*xmlpath.Node
	var path []rune
	var r rune
	var index, i int
	var ok bool
	var parsable *parsableExpression
	var lastDecendent *xmlpath.Node
	var iter *xmlpath.Iter
	var compiledPath *xmlpath.Path
	var err error
	var pathString string
	var simplePath []rune
	length = simplePaths.Len()
	xmlNodes = make([]*xmlpath.Node, 0, 5)
	if length > 0 && simplePathIndex < length {
		if simplePathIndex == 0 {
			simplePath = simplePaths.AtIndex(simplePathIndex)
		} else {
			path = simplePaths.AtIndex(simplePathIndex)
			simplePath = make([]rune, 1, len(path)+1)
			if path[0] == parenStartRune {
				simplePath[0] = parenStartRune
				for _, r = range path {
					simplePath = append(simplePath, r)
				}
				simplePath[1] = '.'
			} else {
				simplePath[0] = '.'
				for _, r = range path {
					simplePath = append(simplePath, r)
				}
			}
		}
		parsable = newParsableExpression(simplePath)
		// paren indexed : (/p)[2]
		path, index, ok = parsable.extractParenIndexed()
		if ok {
			pathString = data.RunesToUTF8String(path)
			compiledPath, err = xmlpath.Compile(pathString)
			if err == nil {
				iter = compiledPath.Iter(context)
				index--
				for i = 0; i <= index; i++ {
					if iter.Next() {
						if i == index {
							lastDecendent = iter.Node()
							if simplePathIndex == length-1 {
								// this is the node we want
								// its the last decendent
								xmlNodes = append(xmlNodes, lastDecendent)
							} else {
								// node is not the node we want
								// it needs to find the last decendents
								// using the rest of the paths in simplePaths
								xmlNodes = findDecendents(simplePaths, simplePathIndex+1, lastDecendent)
							}
						}
					} else {
						break
					}
				}
			}
		} else {
			// this is just a simple path
			pathString = data.RunesToUTF8String(simplePath)
			compiledPath, err = xmlpath.Compile(pathString)
			if err == nil {
				iter = compiledPath.Iter(context)
				for iter.Next() {
					lastDecendent = iter.Node()
					if simplePathIndex == length-1 {
						// this is the node we want
						// its the last decendent
						xmlNodes = append(xmlNodes, lastDecendent)
					} else {
						// node is not the node we want
						// it needs to find the last decendents
						tempNodes = findDecendents(simplePaths, simplePathIndex+1, lastDecendent)
						for _, lastDecendent = range tempNodes {
							xmlNodes = append(xmlNodes, lastDecendent)
						}
					}
				}
			}
		}
	}
	return xmlNodes
}
